package dkbta.ontology.elements.abstractions;

import java.util.List;
import java.util.Set;

import dkbta.ontology.Ontology;
import dkbta.ontology.elements.ElementDef;
import dkbta.ontology.elements.ElementType;
import dkbta.ontology.elements.abstractions.interpolation.PersistenceFunction;
import dkbta.ontology.elements.context.ContextDef;
import dkbta.ontology.loading.OntologyException;
import dkbta.ontology.values.ScaledSymbolicValues;
import dkbta.util.time.TimeInterval;

public abstract class AbstractionDef extends ElementDef{
	/** The interpolation for the abstraction */
	protected final PersistenceFunction _interpolation;

	/** The necessary contexts for this abstraction */
	protected final String[] _necessaryContexts;

	/** The temporal semantics properties of this abstraction */
	protected final Set<TemporalSemanticProperty> _tempSemProperties;

	/**
	 * Initializes an abstraction with the given name and component type
	 * 
	 * @param elementType The element's type
	 * @param name The element's name
	 * @param componentType The element's component type
	 * @param possibleValues The possible values the element can have
	 * @param necessaryContexts The contexts needed for the abstraction
	 * @param tempSemProperties The temporal semantics properties of this abstraction
	 * @param persistFunc The abstraction's persistence function
	 */
	public AbstractionDef(ElementType elementType, String name, String componentType,
		ScaledSymbolicValues possibleValues, Set<String> necessaryContexts,
		Set<TemporalSemanticProperty> tempSemProperties, PersistenceFunction persistFunc){
		super(elementType, name, componentType, possibleValues);
		_necessaryContexts = necessaryContexts.toArray(new String[necessaryContexts
				.size()]);
		_tempSemProperties = tempSemProperties;
		_interpolation = persistFunc;
	}

	@Override
	public void validate(Ontology ontology) throws OntologyException{
		for (String context : _necessaryContexts){
			ContextDef contextDef = ontology.getContext(context);
			if (contextDef == null){
				throw new OntologyException(
						"The abstraction has a non-existing necessary-context: "
								+ context);
			}
			_prerequisiteElements.add(contextDef);
		}
	}

	/**
	 * Performs an interpolation on the given abstraction instances. The updates are done
	 * on the same list.
	 * 
	 * @param abstractions The list of abstractions to be interpolated
	 */
	protected void performInterpolation(List<? extends Abstraction> abstractions){
		if (abstractions.isEmpty()){
			return; // Nothing to interpolate
		}

		Abstraction beforeAbs = abstractions.get(0);
		TimeInterval beforeTI = beforeAbs.getTimeInterval();
		Object beforeValue = beforeAbs.getValue();
		for (int index = 1; index < abstractions.size(); ++index){
			Abstraction afterAbs = abstractions.get(index);
			TimeInterval afterTI = afterAbs.getTimeInterval();
			Object afterValue = afterAbs.getValue();

			if (canBeJoined(beforeTI, beforeValue, afterTI, afterValue)){
				beforeTI.setEndTime(afterTI.getEndTime());
				abstractions.remove(index);
				// Adjusting the return index for the removal of the element
				--index;

				// Iterating backwards and attempting to interpolate
				// previous intervals (we have a chance due to the current interpolation)
				// which may have increased the maximum gap
				afterTI = beforeTI;
				afterValue = beforeValue;
				// Index = the element we now have as "before", will be the "after"
				// Index - 1 = the element we need to have as the new "before"
				for (int j = index - 1; j >= 0; --j){
					beforeAbs = abstractions.get(j);
					beforeTI = beforeAbs.getTimeInterval();
					beforeValue = beforeAbs.getValue();
					if (canBeJoined(beforeTI, beforeValue, afterTI, afterValue)){
						afterTI.setStartTime(beforeTI.getStartTime());
						abstractions.remove(j);
						// Adjusting the return index for the removal of the interval
						--index;
					}else{
						break;
					}
				}
			}
			// The "after" interval now will be the "before" interval in the
			// next iteration
			beforeTI = afterTI;
			beforeValue = afterValue;
		}
	}

	/**
	 * Checks whether the given two time intervals can be joined.
	 * Joining is possible if the values are the same and the persistence function for the
	 * mutual value allows the joining
	 * 
	 * @param beforeTI The earlier of the two intervals
	 * @param beforeValue The value of the earlier of the two intervals
	 * @param afterTI The later of the two intervals
	 * @param afterValue The value of the later of the two intervals
	 * @return Whether the two intervals can be joined
	 */
	private boolean canBeJoined(TimeInterval beforeTI, Object beforeValue,
		TimeInterval afterTI, Object afterValue){
		return beforeValue.equals(afterValue)
				&& _interpolation.canBeJoined(afterValue, beforeTI, afterTI);
	}

	@Override
	public String completeToString(){
		StringBuilder sb = new StringBuilder(super.toString());
		sb.append("\r\nNecessary Contexts:");
		for (String context : _necessaryContexts){
			sb.append("\r\n\t");
			sb.append(context);
		}
		sb.append("\r\nTemporal Semantic Properties: ");
		sb.append(_tempSemProperties);
		sb.append("\r\n").append(_interpolation);

		return sb.toString();
	}
}
